package com.crashcollection.lib;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.crashcollection.configure.Configure;
import com.crashcollection.util.ftp.UploadFileService;
import com.crashcollection.util.http.CrashInfoSQLiteHelper;
import com.crashcollection.util.http.UploadCrashInfoService; 

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 * 提供 
 * 1.本地文件上传FTP   
 * 2.数据库写入.服务器接收
 * 两种处理方式
 * @author user
 * 
 */
public class CrashHandler implements UncaughtExceptionHandler {

	public static final String TAG = "CrashHandler";
	
	public static String BasePath = "";

	private Thread.UncaughtExceptionHandler mDefaultHandler;
	private static CrashHandler INSTANCE = new CrashHandler();
	private static Context mContext;
	private Map<String, String> infos = new HashMap<String, String>();
	
	public static int CRASH_type = 3;                             // 异常处理类型 - 默认生成本地文件 上传
	public static final int CRASH_File_DB_NOUP = 0;               // 生成本地文件.不上传本地文件   -  写入数据库 发送到服务器     
	public static final int CRASH_File_DB_UP = 1;                 // 生成本地文件.上传本地文件   -  写入数据库 发送到服务器     
	public static final int CRASH_File_NOUP = 2;                  // 生成本地文件.不上传本地文件
	public static final int CRASH_File_UP = 3;                    // 生成本地文件.上传本地文件
	public static final int CRASH_DB_UP = 4;                      // 不生成本地文件  - 写入数据库 发送到服务器     

	private CrashHandler() {
	}

	public static CrashHandler getInstance() {
		
		return INSTANCE;
	}
	
	/**
	 * init
	 * @param context
	 * @param loaclfiletype
	 */
	public void init(Context context,int type) {
		mContext = context;
		CRASH_type = type;
		
		// 获取本地文件存储路径
		BasePath = getPath() + "/";
		
		try {
			// 是否处于调试状态 - 调试则不处理
//			if (isDebuggable(context)) {
//				return; 
//			}
			
			mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
			Thread.setDefaultUncaughtExceptionHandler(this);
			
			Intent intent1;
			Intent intent2;
			
			switch (CRASH_type) {
			// 生成本地文件.不上传本地文件   -  写入数据库 发送到服务器     
			case CRASH_File_DB_NOUP:
				intent2 = new Intent(context, UploadCrashInfoService.class);
				context.startService(intent2);
				break;
			// 生成本地文件.上传本地文件   -  写入数据库 发送到服务器
			case CRASH_File_DB_UP:
				intent2 = new Intent(context, UploadCrashInfoService.class);
				context.startService(intent2);
				intent1 = new Intent(context, UploadFileService.class);
				context.startService(intent1);
				break;
			// 生成本地文件.不上传本地文件
			case CRASH_File_NOUP:
				break;
			// 生成本地文件.上传本地文件
			case CRASH_File_UP:
				intent1 = new Intent(context, UploadFileService.class);
				context.startService(intent1);
				break;
			// 不生成本地文件  - 写入数据库 发送到服务器 	
			case CRASH_DB_UP:
				intent2 = new Intent(context, UploadCrashInfoService.class);
				context.startService(intent2);
				break;
			default:
				break;
			}
		} catch (Exception e) {
		}
	}

	/**
	 * 是否处于调试状态
	 * @param context
	 * @return
	 */
	private boolean isDebuggable(Context context) {
		int flags = context.getApplicationInfo().flags;
		flags &= ApplicationInfo.FLAG_DEBUGGABLE;
		return (flags != 0);
	}

	/**
	 * 当UncaughtException发生时会转入该函数来处理
	 */
	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		// TODO delete after
		ex.printStackTrace();
		boolean isMainThread = (Looper.getMainLooper().getThread() == thread);

		if (!handleException(ex, isMainThread) && mDefaultHandler != null) {
			mDefaultHandler.uncaughtException(thread, ex);
		} else {
			if (isMainThread) {
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					Log.e(TAG, "error : ", e);
				} 
				// 报告结束  - 退出程序
				android.os.Process.killProcess(android.os.Process.myPid());
				System.exit(1);
				
			}
		}
	}

	/**
	 * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
	 * 
	 * @param ex
	 * @return true:如果处理了该异常信息;否则返回false.
	 */
	private boolean handleException(Throwable ex, boolean isMainThread) {
		if (ex == null) {
			return false;
		}
		
		// 使用Toast来显示异常信息
		if (isMainThread) {
			new Thread() {
				@Override
				public void run() {
					Looper.prepare();
					Toast.makeText(mContext, "EXCPTION:Collecting...Than.Exit",Toast.LENGTH_LONG).show();
					Looper.loop();
				}
			}.start();
		}

		// 收集设备参数信息
		collectDeviceInfo();
		
		switch (CRASH_type) {
		// 生成本地文件.不上传本地文件   -  写入数据库 发送到服务器     
		case CRASH_File_DB_NOUP:
			saveCrashInfo2DB(ex);
			saveCatchInfo2File(ex);
			break;
		// 生成本地文件.上传本地文件   -  写入数据库 发送到服务器
		case CRASH_File_DB_UP:
			saveCrashInfo2DB(ex);
			saveCatchInfo2File(ex);
			break;
		// 生成本地文件.不上传本地文件
		case CRASH_File_NOUP:
			saveCatchInfo2File(ex);
			break;
		// 生成本地文件.上传本地文件
		case CRASH_File_UP:
			saveCatchInfo2File(ex);
			break;
		// 不生成本地文件  - 写入数据库 发送到服务器 	
		case CRASH_DB_UP:
			saveCrashInfo2DB(ex);
			break;
		default:
			break;
		}
		
		return true;
	}

	/**
	 * 收集设备参数信息
	 * @param ctx
	 */
	private void collectDeviceInfo() {
		try {
			PackageManager pm = mContext.getPackageManager();
			PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
			if (pi != null) {
				String versionName = pi.versionName == null ? "null" : pi.versionName;
				String versionCode = pi.versionCode + "";
				infos.put("versionName", versionName);
				infos.put("versionCode", versionCode);
			}
		} catch (NameNotFoundException e) {
			Log.e(TAG, "an error occured when collect package info", e);
		}
		Field[] fields = Build.class.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				infos.put(field.getName(), field.get(null).toString());
				Log.d(TAG, field.getName() + " : " + field.get(null));
			} catch (Exception e) {
				Log.e(TAG, "an error occured when collect crash info", e);
			}
		}
	}

	/**
	 * 保存错误信息到数据库中
	 * 
	 * @param ex
	 * @return
	 */
	private void saveCrashInfo2DB(Throwable ex) {
		String stack = getStack(ex);
		int pos = stack.indexOf('\n');
		String crashType = stack.substring(0, pos);

		CrashInfo crashInfo = new CrashInfo();
		crashInfo.setAndroidSdk(Build.VERSION.SDK_INT);
		crashInfo.setAndroidRelease(Build.VERSION.RELEASE);
		crashInfo.setLanguage(Locale.getDefault().getLanguage());
		int appVersion = getVersionCode(mContext);
		crashInfo.setAppVersion(appVersion);
		crashInfo.setNetworkType(getNetworkType(mContext));
		crashInfo.setCrashType(crashType);
		crashInfo.setCrashInfo(getCrashInfo(stack));
		crashInfo.setCrashStackMd5(appVersion, stack);

		long timeMillis = System.currentTimeMillis();
		long timeSeconds = TimeUnit.MILLISECONDS.toSeconds(timeMillis);
		crashInfo.setCrashTime(timeSeconds);

		new CrashInfoSQLiteHelper(mContext).insertCrashInfo(crashInfo);
	}

	/**
	 * 获取版本Code
	 * @param context
	 * @return
	 */
	private int getVersionCode(Context context) {
		PackageInfo packageInfo = null;
		PackageManager packageManager = context.getPackageManager();
		try {
			String packageName = context.getPackageName();
			packageInfo = packageManager.getPackageInfo(packageName,PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
		} catch (NameNotFoundException e) {
		}

		if (packageInfo == null) {
			return -1;
		} else {
			return packageInfo.versionCode;
		}
	}

	/**
	 * 获取网络类型
	 * @param context
	 * @return
	 */
	private String getNetworkType(Context context) {
		String networkType;
		ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo ni = cm.getActiveNetworkInfo();
		if (ni == null) {
			networkType = "unknown";
		} else {
			networkType = ni.getTypeName();
		}
		return networkType;
	}

	/**
	 * 获取stack
	 * @param ex
	 * @return
	 */
	private String getStack(Throwable ex) {
		Writer writer = new StringWriter();
		PrintWriter printWriter = new PrintWriter(writer);
		ex.printStackTrace(printWriter);
		Throwable cause = ex.getCause();
		while (cause != null) {
			cause.printStackTrace(printWriter);
			cause = cause.getCause();
		}
		printWriter.close();
		return writer.toString();
	}

	/**
	 * 获取异常内容
	 * @param stack
	 * @return
	 */
	private String getCrashInfo(String stack) {
		StringBuffer sb = new StringBuffer();
		for (Map.Entry<String, String> entry : infos.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			sb.append(key + "=" + value + "\n");
		}
		sb.append("\n");
		sb.append(stack);

		return sb.toString();
	}
	
	 /**  
     * 保存错误信息到文件中  
     *   
     * @param ex  
     * @return  返回文件名称,便于将文件传送到服务器  
     */    
    private String saveCatchInfo2File(Throwable ex) {    
    	
    	// 写入数据
        StringBuffer sb = new StringBuffer();    
        for (Map.Entry<String, String> entry : infos.entrySet()) {    
            String key = entry.getKey();    
            String value = entry.getValue();    
            sb.append(key + "=" + value + "\n");    
        }    
            
        Writer writer = new StringWriter();    
        PrintWriter printWriter = new PrintWriter(writer);    
        ex.printStackTrace(printWriter);    
        Throwable cause = ex.getCause();    
        while (cause != null) {    
            cause.printStackTrace(printWriter);    
            cause = cause.getCause();    
        }    
        printWriter.close();    
        String result = writer.toString();    
        sb.append(result);  
        
        // 写入文件
        try {    
            long timestamp = System.currentTimeMillis();    
            DateFormat formatter = new SimpleDateFormat("MM-dd-HH-mm");    
			String time = formatter.format(new Date());
			String fileName = Configure.Str_App_flag + time + "-" + timestamp + ".log";

			String path = getPath();

			if (path == null) {
				return null;
			}
			FileOutputStream fos = new FileOutputStream(path +  "/"  + fileName);
			fos.write(sb.toString().getBytes());
			fos.close();
			return fileName;  
        } catch (Exception e) {    
            Log.e(TAG, "an error occured while writing file...", e);    
        }    
        return null;    
    }   
    
    /**
     * 获取文件存储路径 - 默认sd根目录创建 同名文件夹  sd/program/crash
     * @return
     */
    private  String getPath() {
		File sdDir = null;
		boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); // 判断sd卡是否存在
		if (sdCardExist) {
			
			sdDir = Environment.getExternalStorageDirectory();  
			
			String str = getApplicationName();
			
			String fileName = sdDir + "/" + str + "/crash";
			File destDir = new File(fileName);
			if (!destDir.exists()) {
				destDir.mkdirs();
			}
			
			return fileName;
		}
		return null;
	}
	
	/**
	 * 获取程序名
	 * @return
	 */
	private  String getApplicationName() {
		PackageManager packageManager = null;
		ApplicationInfo applicationInfo = null;
		try {
			packageManager = mContext.getApplicationContext().getPackageManager();
			applicationInfo = packageManager.getApplicationInfo(mContext.getPackageName(), 0);
		} catch (PackageManager.NameNotFoundException e) {
			applicationInfo = null;
		}
		String applicationName = (String) packageManager.getApplicationLabel(applicationInfo);
		return applicationName;
	}
}
